
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * AUTO-GENERATED FILE. DO NOT MODIFY.
 */

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
import * as zrUtil from 'zrender/lib/core/util.js';
import BoundingRect from 'zrender/lib/core/BoundingRect.js';
import * as visualSolution from '../../visual/visualSolution.js';
import { makeBrushCommonSelectorForSeries } from './selector.js';
import * as throttleUtil from '../../util/throttle.js';
import BrushTargetManager from '../helper/BrushTargetManager.js';
var STATE_LIST = ['inBrush', 'outOfBrush'];
var DISPATCH_METHOD = '__ecBrushSelect';
var DISPATCH_FLAG = '__ecInBrushSelectEvent';

export function layoutCovers(ecModel) {
	ecModel.eachComponent({
		mainType: 'brush'
	}, function (brushModel) {
		var brushTargetManager = brushModel.brushTargetManager = new BrushTargetManager(brushModel.option, ecModel);
		brushTargetManager.setInputRanges(brushModel.areas, ecModel);
	});
}
/**
 * Register the visual encoding if this modules required.
 */

export default function brushVisual(ecModel, api, payload) {
	var brushSelected = [];
	var throttleType;
	var throttleDelay;
	ecModel.eachComponent({
		mainType: 'brush'
	}, function (brushModel) {
		payload && payload.type === 'takeGlobalCursor' && brushModel.setBrushOption(payload.key === 'brush' ? payload.brushOption : {
			brushType: false
		});
	});
	layoutCovers(ecModel);
	ecModel.eachComponent({
		mainType: 'brush'
	}, function (brushModel, brushIndex) {
		var thisBrushSelected = {
			brushId: brushModel.id,
			brushIndex: brushIndex,
			brushName: brushModel.name,
			areas: zrUtil.clone(brushModel.areas),
			selected: []
		}; // Every brush component exists in event params, convenient
		// for user to find by index.

		brushSelected.push(thisBrushSelected);
		var brushOption = brushModel.option;
		var brushLink = brushOption.brushLink;
		var linkedSeriesMap = [];
		var selectedDataIndexForLink = [];
		var rangeInfoBySeries = [];
		var hasBrushExists = false;

		if (!brushIndex) {
			// Only the first throttle setting works.
			throttleType = brushOption.throttleType;
			throttleDelay = brushOption.throttleDelay;
		} // Add boundingRect and selectors to range.

		var areas = zrUtil.map(brushModel.areas, function (area) {
			var builder = boundingRectBuilders[area.brushType];
			var selectableArea = zrUtil.defaults({
				boundingRect: builder ? builder(area) : void 0
			}, area);
			selectableArea.selectors = makeBrushCommonSelectorForSeries(selectableArea);
			return selectableArea;
		});
		var visualMappings = visualSolution.createVisualMappings(brushModel.option, STATE_LIST, function (mappingOption) {
			mappingOption.mappingMethod = 'fixed';
		});
		zrUtil.isArray(brushLink) && zrUtil.each(brushLink, function (seriesIndex) {
			linkedSeriesMap[seriesIndex] = 1;
		});

		function linkOthers(seriesIndex) {
			return brushLink === 'all' || !!linkedSeriesMap[seriesIndex];
		} // If no supported brush or no brush on the series,
		// all visuals should be in original state.

		function brushed(rangeInfoList) {
			return !!rangeInfoList.length;
		}
		/**
     * Logic for each series: (If the logic has to be modified one day, do it carefully!)
     *
     * ( brushed ┬ && ┬hasBrushExist ┬ && linkOthers  ) => StepA: ┬record, ┬ StepB: ┬visualByRecord.
     *   !brushed┘    ├hasBrushExist ┤                            └nothing,┘        ├visualByRecord.
     *                └!hasBrushExist┘                                              └nothing.
     * ( !brushed  && ┬hasBrushExist ┬ && linkOthers  ) => StepA:  nothing,  StepB: ┬visualByRecord.
     *                └!hasBrushExist┘                                              └nothing.
     * ( brushed ┬ &&                     !linkOthers ) => StepA:  nothing,  StepB: ┬visualByCheck.
     *   !brushed┘                                                                  └nothing.
     * ( !brushed  &&                     !linkOthers ) => StepA:  nothing,  StepB:  nothing.
     */
		// Step A

		ecModel.eachSeries(function (seriesModel, seriesIndex) {
			var rangeInfoList = rangeInfoBySeries[seriesIndex] = [];
			seriesModel.subType === 'parallel' ? stepAParallel(seriesModel, seriesIndex) : stepAOthers(seriesModel, seriesIndex, rangeInfoList);
		});

		function stepAParallel(seriesModel, seriesIndex) {
			var coordSys = seriesModel.coordinateSystem;
			hasBrushExists = hasBrushExists || coordSys.hasAxisBrushed();
			linkOthers(seriesIndex) && coordSys.eachActiveState(seriesModel.getData(), function (activeState, dataIndex) {
				activeState === 'active' && (selectedDataIndexForLink[dataIndex] = 1);
			});
		}

		function stepAOthers(seriesModel, seriesIndex, rangeInfoList) {
			if (!seriesModel.brushSelector || brushModelNotControll(brushModel, seriesIndex)) {
				return;
			}

			zrUtil.each(areas, function (area) {
				if (brushModel.brushTargetManager.controlSeries(area, seriesModel, ecModel)) {
					rangeInfoList.push(area);
				}

				hasBrushExists = hasBrushExists || brushed(rangeInfoList);
			});

			if (linkOthers(seriesIndex) && brushed(rangeInfoList)) {
				var data_1 = seriesModel.getData();
				data_1.each(function (dataIndex) {
					if (checkInRange(seriesModel, rangeInfoList, data_1, dataIndex)) {
						selectedDataIndexForLink[dataIndex] = 1;
					}
				});
			}
		} // Step B

		ecModel.eachSeries(function (seriesModel, seriesIndex) {
			var seriesBrushSelected = {
				seriesId: seriesModel.id,
				seriesIndex: seriesIndex,
				seriesName: seriesModel.name,
				dataIndex: []
			}; // Every series exists in event params, convenient
			// for user to find series by seriesIndex.

			thisBrushSelected.selected.push(seriesBrushSelected);
			var rangeInfoList = rangeInfoBySeries[seriesIndex];
			var data = seriesModel.getData();
			var getValueState = linkOthers(seriesIndex) ? function (dataIndex) {
				return selectedDataIndexForLink[dataIndex] ? (seriesBrushSelected.dataIndex.push(data.getRawIndex(dataIndex)), 'inBrush') : 'outOfBrush';
			} : function (dataIndex) {
				return checkInRange(seriesModel, rangeInfoList, data, dataIndex) ? (seriesBrushSelected.dataIndex.push(data.getRawIndex(dataIndex)), 'inBrush') : 'outOfBrush';
			}; // If no supported brush or no brush, all visuals are in original state.

			(linkOthers(seriesIndex) ? hasBrushExists : brushed(rangeInfoList)) && visualSolution.applyVisual(STATE_LIST, visualMappings, data, getValueState);
		});
	});
	dispatchAction(api, throttleType, throttleDelay, brushSelected, payload);
}

function dispatchAction(api, throttleType, throttleDelay, brushSelected, payload) {
	// This event will not be triggered when `setOpion`, otherwise dead lock may
	// triggered when do `setOption` in event listener, which we do not find
	// satisfactory way to solve yet. Some considered resolutions:
	// (a) Diff with prevoius selected data ant only trigger event when changed.
	// But store previous data and diff precisely (i.e., not only by dataIndex, but
	// also detect value changes in selected data) might bring complexity or fragility.
	// (b) Use spectial param like `silent` to suppress event triggering.
	// But such kind of volatile param may be weird in `setOption`.
	if (!payload) {
		return;
	}

	var zr = api.getZr();

	if (zr[DISPATCH_FLAG]) {
		return;
	}

	if (!zr[DISPATCH_METHOD]) {
		zr[DISPATCH_METHOD] = doDispatch;
	}

	var fn = throttleUtil.createOrUpdate(zr, DISPATCH_METHOD, throttleDelay, throttleType);
	fn(api, brushSelected);
}

function doDispatch(api, brushSelected) {
	if (!api.isDisposed()) {
		var zr = api.getZr();
		zr[DISPATCH_FLAG] = true;
		api.dispatchAction({
			type: 'brushSelect',
			batch: brushSelected
		});
		zr[DISPATCH_FLAG] = false;
	}
}

function checkInRange(seriesModel, rangeInfoList, data, dataIndex) {
	for (var i = 0, len = rangeInfoList.length; i < len; i++) {
		var area = rangeInfoList[i];

		if (seriesModel.brushSelector(dataIndex, data, area.selectors, area)) {
			return true;
		}
	}
}

function brushModelNotControll(brushModel, seriesIndex) {
	var seriesIndices = brushModel.option.seriesIndex;
	return seriesIndices != null && seriesIndices !== 'all' && (zrUtil.isArray(seriesIndices) ? zrUtil.indexOf(seriesIndices, seriesIndex) < 0 : seriesIndex !== seriesIndices);
}

var boundingRectBuilders = {
	rect: function (area) {
		return getBoundingRectFromMinMax(area.range);
	},
	polygon: function (area) {
		var minMax;
		var range = area.range;

		for (var i = 0, len = range.length; i < len; i++) {
			minMax = minMax || [[Infinity, -Infinity], [Infinity, -Infinity]];
			var rg = range[i];
			rg[0] < minMax[0][0] && (minMax[0][0] = rg[0]);
			rg[0] > minMax[0][1] && (minMax[0][1] = rg[0]);
			rg[1] < minMax[1][0] && (minMax[1][0] = rg[1]);
			rg[1] > minMax[1][1] && (minMax[1][1] = rg[1]);
		}

		return minMax && getBoundingRectFromMinMax(minMax);
	}
};

function getBoundingRectFromMinMax(minMax) {
	return new BoundingRect(minMax[0][0], minMax[1][0], minMax[0][1] - minMax[0][0], minMax[1][1] - minMax[1][0]);
}